//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.eResearch.Ontology.Mapping
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using Microsoft.Research.eResearch.Ontology.Lookup;

    /// <summary>
    /// Facade class for performing ontology lookup and mapping.
    /// </summary>
    public class OntologicalTypeMapperManager
    {
        /// <summary>
        /// Instance to store the mapping of Input record type and mediator type.
        /// </summary>
        private Dictionary<Type, Type> inputTypeLookup = new Dictionary<Type, Type>();

        /// <summary>
        /// Instance to store the mapping of mediator type and the respective instance.
        /// </summary>
        private Dictionary<Type, IOntologicalTypeMapper> mediatorCache = new Dictionary<Type, IOntologicalTypeMapper>();

        /// <summary>
        /// Instance of the ontology lookup table.
        /// </summary>
        private IOntologyLookupTable ontologyLookupTable;

        /// <summary>
        /// Initializes a new instance of the <see cref="OntologicalTypeMapperManager"/> class.
        /// </summary>
        public OntologicalTypeMapperManager()
        {
            // Using the HardcodedOntologyLookupTable for testing
            ontologyLookupTable = new HardCodedOntologyLookupTable();

            this.AddAssemblyReference(Assembly.GetExecutingAssembly());
        } 

        /// <summary>
        /// Adds the mediators available in the <paramref name="assembly"/>.
        /// </summary>
        /// <param name="assembly">Assembly instance containing mediators.</param>
        public void AddAssemblyReference(Assembly assembly)
        {
            this.AddMediatorsFromAssembly(assembly);
        }

        /// <summary>
        /// Removes the mediators available in the <paramref name="assembly"/>.
        /// </summary>
        /// <param name="assembly">Assembly instance containing mediators.</param>
        public void RemoveAssemblyReference(Assembly assembly)
        {
            if (assembly == Assembly.GetExecutingAssembly())
            {
                throw new ArgumentException("Mediators from the currently-executing assembly may not be removed.");
            }

            this.RemoveMediatorsFromAssembly(assembly);
        }

        /// <summary>
        /// Performs the mapping of the specified object by extracting a list of collections of <see cref="ConceptValuePair"/>s.
        /// </summary>
        /// <param name="input">The input object.</param>
        /// <returns>A list of collections of <see cref="SemanticTypeItem"/>s that could be extracted (mapped)
        /// from the specified object.</returns>
        public List<List<ConceptValuePair>> PerformMapping(object input)
        {
            List<List<ConceptValuePair>> messages = new List<List<ConceptValuePair>>();
            Type targetType = input.GetType();
            if (inputTypeLookup.ContainsKey(targetType))
            {
                Type mediatorType = inputTypeLookup[targetType];

                IOntologicalTypeMapper mediator = null;
                if (mediatorCache.ContainsKey(mediatorType))
                {
                    mediator = mediatorCache[mediatorType];
                }
                else
                {
                    mediator = (IOntologicalTypeMapper)Activator.CreateInstance(mediatorType);
                    mediatorCache[mediatorType] = mediator;
                }

                messages = mediator.MapConceptValuePairs(ontologyLookupTable, input);
            }
            else
            {
                // We don't have a mapper for the specified type
            }

            return messages;
        }

        /// <summary>
        /// Adds all of the mediators found in the specified assembly.
        /// </summary>
        /// <param name="assembly">The assembly in which to look for mediators.</param>
        private void AddMediatorsFromAssembly(Assembly assembly)
        {
            foreach (Type mediatorType in assembly.GetTypes())
            {
                if (typeof(IOntologicalTypeMapper).IsAssignableFrom(mediatorType))
                {
                    IEnumerable<OntologicalTypeMapperAttribute> attributes = mediatorType.GetCustomAttributes(true).OfType<OntologicalTypeMapperAttribute>();
                    if (attributes.Count() > 0)
                    {
                        // 1 attribute was found (not more than 1 since AllowMultiple=false)
                        Type targetType = attributes.First().TargetType;
                        if (!inputTypeLookup.ContainsKey(targetType))
                        {
                            inputTypeLookup.Add(targetType, mediatorType);
                        }
                    }
                }
            } 
        }

        /// <summary>
        /// Removes all of the mediators found in the specified assembly.
        /// </summary>
        /// <param name="assembly">The assembly in which to look for mediators.</param>
        private void RemoveMediatorsFromAssembly(Assembly assembly)
        {
            foreach (Type mediatorType in assembly.GetTypes())
            {
                if (typeof(IOntologicalTypeMapper).IsAssignableFrom(mediatorType))
                {
                    IEnumerable<OntologicalTypeMapperAttribute> attributes = mediatorType.GetCustomAttributes(true).OfType<OntologicalTypeMapperAttribute>();
                    if (attributes.Count() > 0)
                    {
                        // 1 attribute was found (not more than 1 since AllowMultiple=false)
                        Type targetType = attributes.First().TargetType;
                        if (inputTypeLookup.ContainsKey(targetType))
                        {
                            inputTypeLookup.Remove(targetType);
                        }
                    }
                }
            }     
        }
    }
}
